package com.caishi.lkx.user.login;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.caishi.lkx.common.Jwt;
import com.caishi.lkx.common.config.DiamondConfig;
import com.caishi.lkx.common.context.UserContext;
import com.caishi.lkx.common.fliter.TokenFilter;
import com.caishi.lkx.common.ienum.DeviceType;
import com.caishi.lkx.common.util.SmsUtilService;
import com.caishi.lkx.user.dto.LoginDto;
import com.caishi.lkx.user.dto.UserDeviceState;
import com.caishi.lkx.user.ienum.status.UserStatus;
import com.caishi.lkx.user.ienum.type.UserAccountType;
import com.caishi.lkx.user.ienum.type.UserChannelType;
import com.caishi.lkx.user.ienum.type.UserType;
import com.caishi.lkx.user.model.*;
import com.caishi.lkx.user.service.*;
import com.caishi.lkx.user.service.impl.UserServiceImpl;
import com.zzw.common.SpringContextHolder;
import com.zzw.common.Wrappers;
import com.zzw.common.exception.BizException;
import com.zzw.common.exception.BizRuntimeException;
import com.zzw.common.filter.BaseMessageFilter;
import com.zzw.common.threadpool.SysThreadPool;
import com.zzw.common.utils.CommonUtil;

import com.caishi.lkx.user.UserResultCode;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.ModelAttribute;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.*;

/**
 * @author by keray
 * date:2021/4/7 4:43 下午
 */
@Service
@Slf4j
@Primary
public class LoginServiceComposite implements LoginService {


    @Resource
    private ILoginInfoService loginInfoService;

    @Resource
    private UserServiceImpl userService;

    @Resource
    private TokenFilter tokenFilter;

    @Resource
    private IUserLogService userLogService;

    @Resource
    private UserContext userContext;

    @Resource
    private IGroupService groupRpcService;
    @Resource
    private IUserDeviceService userDeviceService;

    @Resource
    private IMemberService memberService;

    @Resource
    private IAdminUserService adminUserService;

    @Resource
    private SmsUtilService smsUtilService;
    @Resource
    private DiamondConfig diamondConfig;
    @Resource
    private  IChannelBusinessService channelBusinessService;



    @Override
    public LoginInfoModel loginBase(LoginDto loginDto, HttpServletRequest request, HttpServletResponse response) throws BizException {
        if (loginDto.getType() != null) {
            LoginService loginService = SpringContextHolder.getBean(loginDto.getType().getLoginBeanName());
            return loginService.loginBase(loginDto, request, response);
        }
        List<LoginInfoModel> loginInfoModels = loginInfoService.selectList(Wrappers.<LoginInfoModel>lambdaQuery().select(LoginInfoModel::getLoginType).eq(LoginInfoModel::getAccount, loginDto.getUserAccount()).ne(loginDto.getType() != UserAccountType.phone, LoginInfoModel::getLoginType, UserAccountType.phone));
        if (CollUtil.isEmpty(loginInfoModels)) {
            if (getDtoUserTypes(loginDto).stream().anyMatch(v -> v == UserType.member)) {
                if (userService.mobileExits(loginDto.getUserAccount(), null)) {
                    throw new BizException(UserResultCode.notSetPass);
                }
            }
            throw new BizException(UserResultCode.loginInfoNotFound);
        }
        for (LoginInfoModel infoModel : loginInfoModels) {
            UserAccountType accountType = infoModel.getLoginType();
            LoginService loginService = SpringContextHolder.getBean(accountType.getLoginBeanName());
            try {
                loginDto.setType(accountType);
                return loginService.loginBase(loginDto, request, response);
            } catch (Exception e) {
                if (e instanceof BizRuntimeException) {
                    throw e;
                }
            }
        }
        throw new BizException(UserResultCode.loginAuthFail);
    }

    public IUserModel<?> login(LoginDto loginDto, HttpServletRequest request, HttpServletResponse response) throws BizException {
        LoginInfoModel infoModel = loginBase(loginDto, request, response);
        if (UserAccountType.isThirdLogin(infoModel.getLoginType()) && StrUtil.isEmpty(infoModel.getUserId())) {
            return UserModel.builder().token(Base64.encode(JSON.toJSONString(infoModel).getBytes(StandardCharsets.UTF_8))).build();
        }
        return success(infoModel, loginDto, true, response);
    }


    @Override
    @Transactional
    public IUserModel<?> register(LoginInfoModel loginInfoModel, IUserModel<?> userModel) throws BizException {
        LoginService loginService = SpringContextHolder.getBean(loginInfoModel.getLoginType().getLoginBeanName());
        return loginService.register(loginInfoModel, userModel);
    }

    @Override
    public void loginFail(LoginInfoModel infoModel, LoginDto loginDto) {
        // 记录用户登录失败日志
        recordsLoginLog(infoModel, loginDto, false, userService.getById(infoModel.getUserId()));
        loginInfoService.loginFail(infoModel, loginDto.getDeviceType(), loginDto.getDeviceName());
    }

    public void loginSuccess(LoginInfoModel infoModel, LoginDto loginDto) {
        // 记录用户成功日志
        recordsLoginLog(infoModel, loginDto, true, userService.getById(infoModel.getUserId()));
        loginInfoService.loginSuccess(infoModel, loginDto.getDeviceType(), loginDto.getDeviceName());
    }

    public UserModel success(LoginInfoModel infoModel, LoginDto loginDto, boolean saveLog, HttpServletResponse response) {
        return this.success(infoModel, loginDto, saveLog, response, false);
    }

    public UserModel success(LoginInfoModel infoModel, LoginDto loginDto, boolean saveLog, HttpServletResponse response, Boolean temporary) {
        Map<String, Object> payload = new HashMap<>();
        UserModel userModel = userService.getById(infoModel.getUserId());
        if (userModel.getUserStatus() == UserStatus.off) {
            throw new BizRuntimeException(UserResultCode.accountIsOff);
        }
        payload.put(TokenFilter.TOKEN_USER_ID_KEY, infoModel.getUserId());
        payload.put(TokenFilter.LOGIN_INFO_ID, infoModel.getId());
        payload.put(TokenFilter.TOKEN_TIME_KEY, System.currentTimeMillis());

        payload.put(TokenFilter.TOKEN_USER_GROUP,loginDto.getGroupId());

        if (StrUtil.isBlank(loginDto.getDeviceId())) {
            // 设置http中的UUID
            loginDto.setDeviceId(userContext.getDuid());
        }
        payload.put(BaseMessageFilter.TOKEN_DEVICE_UUID_KEY, loginDto.getDeviceId());
        String version = userService.getUserLoginVersion(userModel.getId());
        if (version == null) {
            version = userService.updateUserLoginVersion(userModel.getId());
        }
        payload.put(TokenFilter.TOKEN_USER_VERSION, version);
        //groupCheck(userModel, payload);
        settingLoginUserType(userModel, loginDto, payload);
        String token = Jwt.createToken(payload);
        if (loginDto.getDeviceType() == DeviceType.pc || loginDto.getDeviceType() == DeviceType.admin || loginDto.getDeviceType() == DeviceType.h5 || loginDto.getDeviceType() == DeviceType.cqrspxpc) {
            tokenFilter.responseSetToken(loginDto.getDeviceType(), response, token, temporary);
            tokenFilter.responseSetUserInfo(userModel, loginDto.getDeviceType(), response);
            userModel.setToken(token);
        } else {
            userModel.setToken(token);
        }
        if (saveLog) {
            saveLog(userModel, loginDto, infoModel);
        }
        userModel.setMobile((CommonUtil.mobileSafe(userModel.getMobile())));
        return userModel;
    }

    public UserModel success(LoginInfoModel infoModel, LoginDto loginDto, boolean saveLog) {
        Map<String, Object> payload = new HashMap<>();
        UserModel userModel = userService.getById(infoModel.getUserId());
        if (userModel.getUserStatus() == UserStatus.off) {
            throw new BizRuntimeException(UserResultCode.accountIsOff);
        }
        payload.put(TokenFilter.TOKEN_USER_ID_KEY, infoModel.getUserId());
        payload.put(TokenFilter.LOGIN_INFO_ID, infoModel.getId());
        payload.put(TokenFilter.TOKEN_TIME_KEY, System.currentTimeMillis());
        payload.put(TokenFilter.TOKEN_USER_GROUP,loginDto.getGroupId());
        if (StrUtil.isBlank(loginDto.getDeviceId())) {
            // 设置http中的UUID
            loginDto.setDeviceId(userContext.getDuid());
        }
        payload.put(BaseMessageFilter.TOKEN_DEVICE_UUID_KEY, loginDto.getDeviceId());
        String version = userService.getUserLoginVersion(userModel.getId());
        if (version == null) {
            version = userService.updateUserLoginVersion(userModel.getId());
        }
        payload.put(TokenFilter.TOKEN_USER_VERSION, version);
        //groupCheck(userModel, payload);
        settingLoginUserType(userModel, loginDto, payload);
        if (saveLog) {
            saveLog(userModel, loginDto, infoModel);
        }
        userModel.setMobile((CommonUtil.mobileSafe(userModel.getMobile())));
        return userModel;
    }

    @Transactional(rollbackFor = Exception.class)
    public IUserModel<?> thirdBindPhone(@ModelAttribute LoginDto loginDto, Boolean checkCode, HttpServletResponse response) throws BizException {
        var mobile = loginDto.getBindPhone();
        if (!smsUtilService.checkCode(mobile, loginDto.getCode(), SmsUtilService.SmsCodeType.bindPhone) && checkCode) {
            throw new BizException(UserResultCode.smsCodeError);
        }
        var credential = loginDto.getCredential();
        var jsonObject = JSON.parseObject(Base64.decodeStr(credential));
        var loginInfo = new LoginInfoModel();
        loginInfo.setAccount(jsonObject.getString("account"));
        loginInfo.setCertificate(jsonObject.getString("certificate"));
        loginInfo.setLoginType(UserAccountType.valueOf(jsonObject.getString("loginType")));
        var user = jsonObject.getObject("user", MemberModel.class);
        user.setMobile(mobile);
        var haveMobileUserId = userService.getUserIdByMobile(mobile);
        if (StrUtil.isNotBlank(haveMobileUserId)) {
            loginInfo = loginInfoService.userAddNewLogin(loginInfo.getAccount(), loginInfo.getCertificate(), loginInfo.getLoginType(), haveMobileUserId);
        } else {
            user.setChannelPointer(loginDto.getDeviceType().name());
            if (loginDto.getDeviceType().equals(DeviceType.wxcx)) {
                user.setChannelType(UserChannelType.miniProgram);
            }
            register(loginInfo, user);
            loginInfoService.userAddNewLogin(mobile, diamondConfig.getUserDefaultPassword(), UserAccountType.username, user.getId());
            loginInfo = loginInfoService.selectUserLoginInfo(user.getId(), loginInfo.getLoginType());

        }
        return success(loginInfo, loginDto, true, response);
    }

    @Transactional(rollbackFor = Exception.class)
    public IUserModel<?> thirdBindPhone(@ModelAttribute LoginDto loginDto, Boolean checkCode) throws BizException {
        var mobile = loginDto.getBindPhone();
        if (!smsUtilService.checkCode(mobile, loginDto.getCode(), SmsUtilService.SmsCodeType.bindPhone) && checkCode) {
            throw new BizException(UserResultCode.smsCodeError);
        }
        var credential = loginDto.getCredential();
        var jsonObject = JSON.parseObject(Base64.decodeStr(credential));
        var loginInfo = new LoginInfoModel();
        loginInfo.setAccount(jsonObject.getString("account"));
        loginInfo.setCertificate(jsonObject.getString("certificate"));
        loginInfo.setLoginType(UserAccountType.valueOf(jsonObject.getString("loginType")));
        var user = jsonObject.getObject("user", MemberModel.class);
        user.setMobile(mobile);
        var haveMobileUserId = userService.getUserIdByMobile(mobile);
        if (StrUtil.isNotBlank(haveMobileUserId)) {
            loginInfo = loginInfoService.userAddNewLogin(loginInfo.getAccount(), loginInfo.getCertificate(), loginInfo.getLoginType(), haveMobileUserId);
        } else {
            register(loginInfo, user);
            loginInfo = loginInfoService.selectUserLoginInfo(user.getId(), loginInfo.getLoginType());
        }
        return success(loginInfo, loginDto, true);
    }

    private void groupCheck(UserModel userModel, Map<String, Object> payload) {
//        PlatformType platformType = userContext.currentPlatform();
//        if (UserType.intAnyHave(userModel.getUserType(), new UserType[]{UserType.groupAdmin})) {
//            if (null != userModel.getHaveType() && userModel.getHaveType().equals(HaveType.ykt.getValue())) {
//                return;
//            }
//            var admin = adminUserService.getById(userModel.getId());
//            if (null == admin) {
//                UserType[] userTypes = UserType.int2Array(userModel.getUserType());
//                UserType[] newUserType = Arrays.stream(userTypes).filter(u -> !u.equals(UserType.groupAdmin)).toArray(UserType[]::new);
//                userModel.setUserType(UserType.array2Int(newUserType));
//                this.groupCheck(userModel, payload);
//                return;
//            }
//            var groupId = admin.getGroupId();
//            GroupModel groupModel = groupRpcService.getById(groupId);
//            if (null == groupModel) {
//                return;
//            }
//            LocalDate time;
////            if (platformType.equals(PlatformType.VERS7)) {
////                time = groupModel.getExpireTime();
////            } else {
////                time = groupModel.getKaoyanExpireTime();
////            }
////            if (null != time && (time.isAfter(LocalDate.now()) || time.isEqual(LocalDate.now()))) {
////                payload.put(TokenFilter.TOKEN_USER_GROUP_TIME, TimeUtil.DATE_TIME_FORMATTER_SC.format(time.atTime(23, 59, 59)));
////                payload.put(TokenFilter.TOKEN_USER_GROUP, groupId);
////            } else {
////                if (admin.getGroupOwner()) {
////                    throw new BizRuntimeException(UserResultCode.groupExpired);
////                }
////            }
//        } else if (UserType.intAnyHave(userModel.getUserType(), new UserType[]{UserType.member, UserType.groupMember})) {
//            var member = memberService.getById(userModel.getId());
//            var groupId = member.getGroupId();
//            if (groupId != null) {
//                // 机构过期时间
//                LocalDateTime t;
//                GroupModel model = groupRpcService.getById(groupId);
//                if (null == model) return;
//                //if (platformType.equals(PlatformType.KAOYAN)) {
//                    //var time = model.getKaoyanExpireTime();
//                   // if (null == time) {
//                      //  t = LocalDateTime.of(1, 1, 1, 1, 1);
//                   // } else {
//                     //   var time1 = time.atTime(23, 59, 59);
//                      //  var time2 = member.getExpireTime();
//                     //   t = null != time2 && time2.isBefore(time1) ? time2 : time1;
//                    }
//
//               // } else {
//                   // var time = model.getExpireTime();
//                   // if (null == time) {
//                      //  t = LocalDateTime.of(1, 1, 1, 1, 1);
//                  //  } else {
//                    //    var time1 = time.atTime(23, 59, 59);
//                    //    var time2 = member.getExpireTime();
//                    //    t = null != time2 && time2.isBefore(time1) ? time2 : time1;
//                   // }
//
//               // }
//                //if (t.isAfter(LocalDateTime.now())) {
//                  //  payload.put(TokenFilter.TOKEN_USER_GROUP_TIME, TimeUtil.DATE_TIME_FORMATTER_SC.format(t));
//                  //  payload.put(TokenFilter.TOKEN_USER_GROUP, groupId);
//                //}
//            }
//        }
    }

    private void settingLoginUserType(UserModel userModel, LoginDto loginDto, Map<String, Object> payload) {
        if (loginDto.getUserType() == null) {
            var types = loginDto.getUserTypes() == null ? new LinkedList<UserType>() : Arrays.asList(loginDto.getUserTypes());
            List<UserType> userTypes = Arrays.asList(userModel.getUserTypes());
            if (loginDto.getDeviceType() == DeviceType.admin) {
                if (userTypes.contains(UserType.admin) && types.contains(UserType.admin)) {
                    payload.put(TokenFilter.TOKEN_USER_TYPE_KEY, UserType.admin.name());
                } else if (userTypes.contains(UserType.agent) && types.contains(UserType.agent)) {
                    payload.put(TokenFilter.TOKEN_USER_TYPE_KEY, UserType.agent.name());
                }else if (userTypes.contains(UserType.channelBusiness) && types.contains(UserType.channelBusiness)) {
                    payload.put(TokenFilter.TOKEN_USER_TYPE_KEY, UserType.channelBusiness.name());
                    payload.put(TokenFilter.TOKEN_USER_GROUP,channelBusinessService.getById(userModel.getId()).getGroupId());
                } else {
                    throw new BizRuntimeException("无权限");
                }
            } else {
              payload.put(TokenFilter.TOKEN_USER_TYPE_KEY, UserType.member.name());}
        } else {
            payload.put(TokenFilter.TOKEN_USER_TYPE_KEY, loginDto.getUserType().name());
        }
        // 设置用户api角色
        String apiRole = userService.getUserRoleName(userModel.getId());
        if (UserType.intHaveType(userModel.getUserType(), UserType.admin)) {
            apiRole += "," + UserType.admin.name();
        }
        payload.put(TokenFilter.TOKEN_ROLE_KEY, apiRole);
    }


    private void saveLog(UserModel userModel, LoginDto loginDto, LoginInfoModel infoModel) {
        // 记录用户成功日志
        userService.updateUserLoginVersionUUid(userModel.getId(), loginDto.getDeviceType(), loginDto.getDeviceId());
        // 通知用户被挤下线
        SysThreadPool.execute(() -> {
            loginSuccess(infoModel, loginDto);
            userDeviceService.settingUserDevice(userModel.getId(), true, loginDto.getDeviceType(), UserDeviceState.builder().id(loginDto.getDeviceId()).aid(loginDto.getDevicePushId()).token(userModel.getToken()).build());
            userContext.clear();
        }, true);
    }


    private void recordsLoginLog(LoginInfoModel infoModel, LoginDto loginDto, boolean success, IUserModel<?> userModel) {
        ISysUserLog.SysUserLog userLog = new ISysUserLog.SysUserLog();
        userLog.setUserId(infoModel.getUserId());
        UserType userType = loginDto.getUserType();
        List<UserType> userTypeList = Arrays.asList(userModel.getUserTypes());
        if (userType == null && userTypeList.contains(UserType.admin)) {
            userType = UserType.admin;
        } else if (userType == null && userTypeList.contains(UserType.agent)) {
            userType = UserType.agent;
        }else if (userType == null) {
            userType = UserType.member;
        }
        if (userType == UserType.admin) {
            userLog.setLogType(ISysUserLog.ADMIN_LOGIN);
        } else if (userType == UserType.agent) {
            userLog.setLogType(ISysUserLog.AGENT_LOGIN);
        } else {
            userLog.setLogType(userType.name());
        }
        userLog.setCreatedTime(LocalDateTime.now());
        userLog.setContent(MapUtil.builder(BeanUtil.beanToMap(loginDto)).put("userId", infoModel.getUserId()).put("loginType", loginDto.getType()).put("account", infoModel.getAccount()).put("certificate", infoModel.getCertificate()).put("userCertificate", loginDto.getCredential()).put("lock", infoModel.getLock()).put("enable", infoModel.getEnable()).put("ip", userContext.currentIp()).put("status", success).build());
        userLogService.putLog(userLog);
    }

    public String resetToken(HttpServletResponse response) {
        var infoId = userContext.currentTokenData().getInfoId();
        var r = this.success(loginInfoService.getById(infoId), LoginDto.builder().deviceId(userContext.currentTokenData().getDeviceUUID()).userType(UserType.valueOf(userContext.currentTokenData().getUserTypeName())).deviceType(userContext.currentTokenData().getDevice()).build(), false, response);
        if (userContext.currentTokenData().getDevice() == DeviceType.pc || userContext.currentTokenData().getDevice() == DeviceType.h5) {
            return null;
        }
        return r.getToken();
    }

    public Boolean removeThirdBind(String currentUserId, UserAccountType type) {
        return loginInfoService.delete(Wrappers.<LoginInfoModel>lambdaUpdate().eq(LoginInfoModel::getUserId, currentUserId).eq(LoginInfoModel::getLoginType, type)) > 0;
    }
}
